/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <assert.h>
#include "../include/splparser.h"

namespace ime_pinyin {

    SpellingParser::SpellingParser() {
        spl_trie_ = SpellingTrie::get_cpinstance();
    }

    bool SpellingParser::is_valid_to_parse ( char ch ) {
        return SpellingTrie::is_valid_spl_char ( ch );
    }

    uint16 SpellingParser::splstr_to_idxs ( const char *splstr, uint16 str_len,
                                            uint16 spl_idx[], uint16 start_pos[],
                                            uint16 max_size, bool &last_is_pre ) {
        if ( NULL == splstr || 0 == max_size || 0 == str_len )
        { return 0; }
        if ( !SpellingTrie::is_valid_spl_char ( splstr[0] ) )
        { return 0; }
        last_is_pre = false;
        const SpellingNode *node_this = spl_trie_->root_;
        uint16 str_pos = 0;
        uint16 idx_num = 0;
        if ( NULL != start_pos )
        { start_pos[0] = 0; }
        bool last_is_splitter = false;
        while ( str_pos < str_len ) {
            char char_this = splstr[str_pos];
            // all characters outside of [a, z] are considered as splitters
            if ( !SpellingTrie::is_valid_spl_char ( char_this ) ) {
                // test if the current node is endable
                uint16 id_this = node_this->spelling_idx;
                if ( spl_trie_->if_valid_id_update ( &id_this ) ) {
                    spl_idx[idx_num] = id_this;
                    idx_num++;
                    str_pos++;
                    if ( NULL != start_pos )
                    { start_pos[idx_num] = str_pos; }
                    if ( idx_num >= max_size )
                    { return idx_num; }
                    node_this = spl_trie_->root_;
                    last_is_splitter = true;
                    continue;
                } else {
                    if ( last_is_splitter ) {
                        str_pos++;
                        if ( NULL != start_pos )
                        { start_pos[idx_num] = str_pos; }
                        continue;
                    } else {
                        return idx_num;
                    }
                }
            }
            last_is_splitter = false;
            SpellingNode *found_son = NULL;
            if ( 0 == str_pos ) {
                if ( char_this >= 'a' )
                { found_son = spl_trie_->level1_sons_[char_this - 'a']; }
                else
                { found_son = spl_trie_->level1_sons_[char_this - 'A']; }
            } else {
                SpellingNode *first_son = node_this->first_son;
                // Because for Zh/Ch/Sh nodes, they are the last in the buffer and
                // frequently used, so we scan from the end.
                for ( int i = 0; i < node_this->num_of_son; i++ ) {
                    SpellingNode *this_son = first_son + i;
                    if ( SpellingTrie::is_same_spl_char (
                             this_son->char_this_node, char_this ) ) {
                        found_son = this_son;
                        break;
                    }
                }
            }
            // found, just move the current node pointer to the the son
            if ( NULL != found_son ) {
                node_this = found_son;
            } else {
                // not found, test if it is endable
                uint16 id_this = node_this->spelling_idx;
                if ( spl_trie_->if_valid_id_update ( &id_this ) ) {
                    // endable, remember the index
                    spl_idx[idx_num] = id_this;
                    idx_num++;
                    if ( NULL != start_pos )
                    { start_pos[idx_num] = str_pos; }
                    if ( idx_num >= max_size )
                    { return idx_num; }
                    node_this = spl_trie_->root_;
                    continue;
                } else {
                    return idx_num;
                }
            }
            str_pos++;
        }
        uint16 id_this = node_this->spelling_idx;
        if ( spl_trie_->if_valid_id_update ( &id_this ) ) {
            // endable, remember the index
            spl_idx[idx_num] = id_this;
            idx_num++;
            if ( NULL != start_pos )
            { start_pos[idx_num] = str_pos; }
        }
        last_is_pre = !last_is_splitter;
        return idx_num;
    }

    uint16 SpellingParser::splstr_to_idxs_f ( const char *splstr, uint16 str_len,
                                              uint16 spl_idx[], uint16 start_pos[],
                                              uint16 max_size, bool &last_is_pre ) {
        uint16 idx_num = splstr_to_idxs ( splstr, str_len, spl_idx, start_pos,
                                          max_size, last_is_pre );
        for ( uint16 pos = 0; pos < idx_num; pos++ ) {
            if ( spl_trie_->is_half_id_yunmu ( spl_idx[pos] ) ) {
                spl_trie_->half_to_full ( spl_idx[pos], spl_idx + pos );
                if ( pos == idx_num - 1 ) {
                    last_is_pre = false;
                }
            }
        }
        return idx_num;
    }

    uint16 SpellingParser::splstr16_to_idxs ( const char16 *splstr, uint16 str_len,
                                              uint16 spl_idx[], uint16 start_pos[],
                                              uint16 max_size, bool &last_is_pre ) {
        if ( NULL == splstr || 0 == max_size || 0 == str_len )
        { return 0; }
        if ( !SpellingTrie::is_valid_spl_char ( splstr[0] ) )
        { return 0; }
        last_is_pre = false;
        const SpellingNode *node_this = spl_trie_->root_;
        uint16 str_pos = 0;
        uint16 idx_num = 0;
        if ( NULL != start_pos )
        { start_pos[0] = 0; }
        bool last_is_splitter = false;
        while ( str_pos < str_len ) {
            char16 char_this = splstr[str_pos];
            // all characters outside of [a, z] are considered as splitters
            if ( !SpellingTrie::is_valid_spl_char ( char_this ) ) {
                // test if the current node is endable
                uint16 id_this = node_this->spelling_idx;
                if ( spl_trie_->if_valid_id_update ( &id_this ) ) {
                    spl_idx[idx_num] = id_this;
                    idx_num++;
                    str_pos++;
                    if ( NULL != start_pos )
                    { start_pos[idx_num] = str_pos; }
                    if ( idx_num >= max_size )
                    { return idx_num; }
                    node_this = spl_trie_->root_;
                    last_is_splitter = true;
                    continue;
                } else {
                    if ( last_is_splitter ) {
                        str_pos++;
                        if ( NULL != start_pos )
                        { start_pos[idx_num] = str_pos; }
                        continue;
                    } else {
                        return idx_num;
                    }
                }
            }
            last_is_splitter = false;
            SpellingNode *found_son = NULL;
            if ( 0 == str_pos ) {
                if ( char_this >= 'a' )
                { found_son = spl_trie_->level1_sons_[char_this - 'a']; }
                else
                { found_son = spl_trie_->level1_sons_[char_this - 'A']; }
            } else {
                SpellingNode *first_son = node_this->first_son;
                // Because for Zh/Ch/Sh nodes, they are the last in the buffer and
                // frequently used, so we scan from the end.
                for ( int i = 0; i < node_this->num_of_son; i++ ) {
                    SpellingNode *this_son = first_son + i;
                    if ( SpellingTrie::is_same_spl_char (
                             this_son->char_this_node, char_this ) ) {
                        found_son = this_son;
                        break;
                    }
                }
            }
            // found, just move the current node pointer to the the son
            if ( NULL != found_son ) {
                node_this = found_son;
            } else {
                // not found, test if it is endable
                uint16 id_this = node_this->spelling_idx;
                if ( spl_trie_->if_valid_id_update ( &id_this ) ) {
                    // endable, remember the index
                    spl_idx[idx_num] = id_this;
                    idx_num++;
                    if ( NULL != start_pos )
                    { start_pos[idx_num] = str_pos; }
                    if ( idx_num >= max_size )
                    { return idx_num; }
                    node_this = spl_trie_->root_;
                    continue;
                } else {
                    return idx_num;
                }
            }
            str_pos++;
        }
        uint16 id_this = node_this->spelling_idx;
        if ( spl_trie_->if_valid_id_update ( &id_this ) ) {
            // endable, remember the index
            spl_idx[idx_num] = id_this;
            idx_num++;
            if ( NULL != start_pos )
            { start_pos[idx_num] = str_pos; }
        }
        last_is_pre = !last_is_splitter;
        return idx_num;
    }

    uint16 SpellingParser::splstr16_to_idxs_f ( const char16 *splstr, uint16 str_len,
                                                uint16 spl_idx[], uint16 start_pos[],
                                                uint16 max_size, bool &last_is_pre ) {
        uint16 idx_num = splstr16_to_idxs ( splstr, str_len, spl_idx, start_pos,
                                            max_size, last_is_pre );
        for ( uint16 pos = 0; pos < idx_num; pos++ ) {
            if ( spl_trie_->is_half_id_yunmu ( spl_idx[pos] ) ) {
                spl_trie_->half_to_full ( spl_idx[pos], spl_idx + pos );
                if ( pos == idx_num - 1 ) {
                    last_is_pre = false;
                }
            }
        }
        return idx_num;
    }

    uint16 SpellingParser::get_splid_by_str ( const char *splstr, uint16 str_len,
                                              bool *is_pre ) {
        if ( NULL == is_pre )
        { return 0; }
        uint16 spl_idx[2];
        uint16 start_pos[3];
        if ( splstr_to_idxs ( splstr, str_len, spl_idx, start_pos, 2, *is_pre ) != 1 )
        { return 0; }
        if ( start_pos[1] != str_len )
        { return 0; }
        return spl_idx[0];
    }

    uint16 SpellingParser::get_splid_by_str_f ( const char *splstr, uint16 str_len,
                                                bool *is_pre ) {
        if ( NULL == is_pre )
        { return 0; }
        uint16 spl_idx[2];
        uint16 start_pos[3];
        if ( splstr_to_idxs ( splstr, str_len, spl_idx, start_pos, 2, *is_pre ) != 1 )
        { return 0; }
        if ( start_pos[1] != str_len )
        { return 0; }
        if ( spl_trie_->is_half_id_yunmu ( spl_idx[0] ) ) {
            spl_trie_->half_to_full ( spl_idx[0], spl_idx );
            *is_pre = false;
        }
        return spl_idx[0];
    }

    uint16 SpellingParser::get_splids_parallel ( const char *splstr, uint16 str_len,
                                                 uint16 splidx[], uint16 max_size,
                                                 uint16 &full_id_num, bool &is_pre ) {
        if ( max_size <= 0 || !is_valid_to_parse ( splstr[0] ) )
        { return 0; }
        splidx[0] = get_splid_by_str ( splstr, str_len, &is_pre );
        full_id_num = 0;
        if ( 0 != splidx[0] ) {
            if ( splidx[0] >= kFullSplIdStart )
            { full_id_num = 1; }
            return 1;
        }
        return 0;
    }

}  // namespace ime_pinyin
